﻿#region Using Statements
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
#endregion

namespace GameBase.Inputs
{
    /// <summary>
    /// Basic functionality needed for player input tracking.
    /// <para>Tracking of both current and previous states of input devices.</para>
    /// <para>Use this class for higher level input management.</para>
    /// </summary>
    public sealed class InputState
    {
        #region Singleton Pattern

        // the only instance of this class... eager initialization
        static readonly InputState instance = new InputState();

        // private constructor...
        // ... creates a new instance of this class
        private InputState()
        {
            CurrentGamepadState = new GamePadState[NUM_PLAYER];
            LastGamepadState = new GamePadState[NUM_PLAYER];
        }

        /// <summary>
        /// Gets a common instance of this class.
        /// </summary>
        public static InputState Instance
        {
            get { return instance; }
        }

        #endregion

        #region Keyboard Fields

        // keyboard... current, previous states
        KeyboardState CurrentKeyboardState;
        KeyboardState LastKeyboardState;

        #endregion

        #region Gamepad Fields

        // max number of gamepads allowed
        readonly int NUM_PLAYER = Enum.GetNames(typeof(PlayerIndex)).Length;

        // gamepad... current, previous states
        GamePadState[] CurrentGamepadState;
        GamePadState[] LastGamepadState;

        #endregion

        #region Update

        /// <summary>
        /// Updates the state of input devices.
        /// </summary>
        public void Update()
        {
            #region Keyboard Polling

            // save previous keyboard state
            LastKeyboardState = CurrentKeyboardState;

            // obtain current keyboard state
            CurrentKeyboardState = Keyboard.GetState();

            #endregion

            #region Gamepad Polling

            for (int i = 0; i < NUM_PLAYER; i++)
            {
                // save previous gamepad state
                LastGamepadState[i] = CurrentGamepadState[i];

                // obtain current gamepad state
                CurrentGamepadState[i] = GamePad.GetState((PlayerIndex)i);
            }

            #endregion

            #region Double Tap

            // update timer for double-tap detection
            keyDbTapTimer++;
            btnDbTapTimer++;

            #endregion
        }

        #endregion

        #region Keyboard Helper Methods

        /// <summary>
        /// Helper for checking if a key was newly pressed during the current frame.
        /// </summary>
        /// <param name="key">Key press queried.</param>
        /// <param name="isToggled">Key press mode. In toggle mode, separate key presses will be detected.</param>
        /// <returns>Key pressed.</returns>
        public bool IsKeyPress(Keys key, bool isToggled)
        {
            if (isToggled)
                return (CurrentKeyboardState.IsKeyDown(key) && LastKeyboardState.IsKeyUp(key));
            else
                return CurrentKeyboardState.IsKeyDown(key);
        }

        #endregion

        #region Gamepad Helper Methods

        /// <summary>
        /// Helper for checking if a gamepad button was newly pressed during the current frame.
        /// </summary>
        /// <param name="button">Button press queried.</param>
        /// <param name="playerIndex">Reports which player pressed the specified button.</param>
        /// <param name="isToggled">Button press mode. In toggle mode, separate button presses will be detected.</param>
        /// <returns>Button pressed.</returns>
        public bool IsButtonPress(Buttons button, out PlayerIndex playerIndex, bool isToggled)
        {
            playerIndex = PlayerIndex.One;

            for (int i = 0; i < NUM_PLAYER; i++)
            {
                if (IsButtonPress(button, (PlayerIndex)i, isToggled))
                {
                    playerIndex = (PlayerIndex)i;
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Helper for checking if a gamepad button was newly pressed during the current frame.
        /// </summary>
        /// <param name="button">Button press queried.</param>
        /// <param name="playerIndex">Player from which read input.</param>
        /// <param name="isToggled">Button press mode. In toggle mode, separate button presses will be detected.</param>
        /// <returns>Button pressed.</returns>
        public bool IsButtonPress(Buttons button, PlayerIndex playerIndex, bool isToggled)
        {
            if (CurrentGamepadState[(int)playerIndex].IsConnected)
            {
                if (isToggled)
                {
                    return (CurrentGamepadState[(int)playerIndex].IsButtonDown(button)
                            && LastGamepadState[(int)playerIndex].IsButtonUp(button));
                }
                else
                {
                    return CurrentGamepadState[(int)playerIndex].IsButtonDown(button);
                }
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Helper for checking if a gamepad thumbstick was newly pressed during the current frame.
        /// </summary>
        /// <param name="thumbstick">Thumbstick press queried.</param>
        /// <param name="playerIndex">Player from which read input.</param>
        /// <param name="threshold">Threshold value for thumbstick press detection.</param>
        /// <param name="isToggled">Thumbstick press mode. In toggle mode, separate thumbstick presses will be detected.</param>
        /// <returns>Thumbstick pressed.</returns>
        public bool IsThumbStickPress(Buttons thumbstick, PlayerIndex playerIndex, float threshold, bool isToggled)
        {
            bool isThumbStickPress;

            if (CurrentGamepadState[(int)playerIndex].IsConnected)
            {
                Vector2[] currentThumbStickState = { CurrentGamepadState[(int)playerIndex].ThumbSticks.Left,
                                                     CurrentGamepadState[(int)playerIndex].ThumbSticks.Right };

                Vector2[] lastThumbStickState = { LastGamepadState[(int)playerIndex].ThumbSticks.Left,
                                                  LastGamepadState[(int)playerIndex].ThumbSticks.Right };

                switch (thumbstick)
                {
                    case Buttons.LeftThumbstickDown:
                        {
                            if (isToggled)
                                isThumbStickPress = (currentThumbStickState[0].Y < -threshold && lastThumbStickState[0].Y > -threshold);
                            else
                                isThumbStickPress = (currentThumbStickState[0].Y < -threshold);
                            break;
                        }
                    case Buttons.LeftThumbstickUp:
                        {
                            if (isToggled)
                                isThumbStickPress = (currentThumbStickState[0].Y > threshold && lastThumbStickState[0].Y < threshold);
                            else
                                isThumbStickPress = (currentThumbStickState[0].Y > threshold);
                            break;
                        }
                    case Buttons.LeftThumbstickLeft:
                        {
                            if (isToggled)
                                isThumbStickPress = (currentThumbStickState[0].X < -threshold && lastThumbStickState[0].X > -threshold);
                            else
                                isThumbStickPress = (currentThumbStickState[0].X < -threshold);
                            break;
                        }
                    case Buttons.LeftThumbstickRight:
                        {
                            if (isToggled)
                                isThumbStickPress = (currentThumbStickState[0].X > threshold && lastThumbStickState[0].X < threshold);
                            else
                                isThumbStickPress = (currentThumbStickState[0].X > threshold);
                            break;
                        }
                    case Buttons.RightThumbstickDown:
                        {
                            if (isToggled)
                                isThumbStickPress = (currentThumbStickState[1].Y < -threshold && lastThumbStickState[1].Y > -threshold);
                            else
                                isThumbStickPress = (currentThumbStickState[1].Y < -threshold);
                            break;
                        }
                    case Buttons.RightThumbstickUp:
                        {
                            if (isToggled)
                                isThumbStickPress = (currentThumbStickState[1].Y > threshold && lastThumbStickState[1].Y < threshold);
                            else
                                isThumbStickPress = (currentThumbStickState[1].Y > threshold);
                            break;
                        }
                    case Buttons.RightThumbstickLeft:
                        {
                            if (isToggled)
                                isThumbStickPress = (currentThumbStickState[1].X < -threshold && lastThumbStickState[1].X > -threshold);
                            else
                                isThumbStickPress = (currentThumbStickState[1].X < -threshold);
                            break;
                        }
                    case Buttons.RightThumbstickRight:
                        {
                            if (isToggled)
                                isThumbStickPress = (currentThumbStickState[1].X > threshold && lastThumbStickState[1].X < threshold);
                            else
                                isThumbStickPress = (currentThumbStickState[1].X > threshold);
                            break;
                        }
                    default:
                        isThumbStickPress = false;
                        break;
                }
            }
            else
            {
                isThumbStickPress = false;
            }

            return isThumbStickPress;
        }

        /// <summary>
        /// Helper for checking if a gamepad trigger was newly pressed during the current frame.
        /// </summary>
        /// <param name="trigger">Trigger press queried.</param>
        /// <param name="playerIndex">Player from which read input.</param>
        /// <param name="threshold">Threshold value for trigger press detection.</param>
        /// <param name="isToggled">Trigger press mode. In toggle mode, separate trigger presses will be detected.</param>
        /// <returns>Trigger pressed.</returns>
        public bool IsTriggerPress(Buttons trigger, PlayerIndex playerIndex, float threshold, bool isToggled)
        {
            bool isTriggerPress;

            if (CurrentGamepadState[(int)playerIndex].IsConnected)
            {
                float[] currentTriggerState = { CurrentGamepadState[(int)playerIndex].Triggers.Left,
                                                CurrentGamepadState[(int)playerIndex].Triggers.Right };

                float[] lastTriggerState = { LastGamepadState[(int)playerIndex].Triggers.Left,
                                             LastGamepadState[(int)playerIndex].Triggers.Right };

                switch (trigger)
                {
                    case Buttons.LeftTrigger:
                        {
                            if (isToggled)
                                isTriggerPress = (currentTriggerState[0] > threshold && lastTriggerState[0] < threshold);
                            else
                                isTriggerPress = (currentTriggerState[0] > threshold);
                            break;
                        }
                    case Buttons.RightTrigger:
                        {
                            if (isToggled)
                                isTriggerPress = (currentTriggerState[1] > threshold && lastTriggerState[1] < threshold);
                            else
                                isTriggerPress = (currentTriggerState[1] > threshold);
                            break;
                        }
                    default:
                        isTriggerPress = false;
                        break;
                }
            }
            else
            {
                isTriggerPress = false;
            }

            return isTriggerPress;
        }

        #endregion

        #region Double Tap

        /// <summary>
        /// Defines all possible double-tap detection process states.
        /// </summary>
        enum DoubleTapState { S0, S1 };

        // double-tap detection process current state
        DoubleTapState keyDbTapState = DoubleTapState.S0;
        DoubleTapState btnDbTapState = DoubleTapState.S0;

        // key or gamepad button which triggered dobule-tap detection process
        Keys lstKeyDbTap;
        Buttons lstBtnDbTap;

        // total number of frames during which double-tap is valid
        readonly int DoubleTapTime = 20;

        // timer for double-tap detection
        int keyDbTapTimer;
        int btnDbTapTimer;

        #region IsKeyDoubleTap

        /// <summary>
        /// Helper for checking whether a key is being double-tapped.
        /// </summary>
        /// <param name="key">Key press queried.</param>
        /// <returns>Key double-tapped.</returns>
        public bool IsKeyDoubleTap(Keys key)
        {
            bool doubleTap = false;

            switch (keyDbTapState)
            {
                case DoubleTapState.S0:
                    {
                        // reset timer
                        keyDbTapTimer = 0;

                        if (IsKeyPress(key, true))
                        {
                            // continue detection process
                            lstKeyDbTap = key;
                            keyDbTapState = DoubleTapState.S1;
                        }

                        break;
                    }
                case DoubleTapState.S1:
                    {
                        if (IsKeyPress(key, true) && (keyDbTapTimer <= DoubleTapTime))
                        {
                            if (key == lstKeyDbTap)
                            {
                                // double-tap sequence complete
                                doubleTap = true;
                                keyDbTapState = DoubleTapState.S0;
                            }
                            else
                            {
                                // double-tap sequence broken
                                doubleTap = false;
                                keyDbTapState = DoubleTapState.S0;
                            }
                        }
                        else if (keyDbTapTimer > DoubleTapTime)
                        {
                            // timeout
                            doubleTap = false;
                            keyDbTapState = DoubleTapState.S0;
                        }

                        break;
                    }
                default:
                    {
                        break;
                    }
            }

            return doubleTap;
        }

        #endregion

        #region IsButtonDoubleTap

        /// <summary>
        /// Helper for checking whether a gamepad button is being double-tapped.
        /// </summary>
        /// <param name="button">Button press queried.</param>
        /// <param name="playerIndex">Player from which read input.</param>
        /// <returns>Button double-tapped.</returns>
        public bool IsButtonDoubleTap(Buttons button, PlayerIndex playerIndex)
        {
            bool doubleTap = false;

            switch (btnDbTapState)
            {
                case DoubleTapState.S0:
                    {
                        // reset timer
                        btnDbTapTimer = 0;

                        if (IsButtonPress(button, playerIndex, true))
                        {
                            // continue detection process
                            lstBtnDbTap = button;
                            btnDbTapState = DoubleTapState.S1;
                        }

                        break;
                    }
                case DoubleTapState.S1:
                    {
                        if (IsButtonPress(button, playerIndex, true) && (btnDbTapTimer <= DoubleTapTime))
                        {
                            if (button == lstBtnDbTap)
                            {
                                // double-tap sequence complete
                                doubleTap = true;
                                btnDbTapState = DoubleTapState.S0;
                            }
                            else
                            {
                                // double-tap sequence broken
                                doubleTap = false;
                                btnDbTapState = DoubleTapState.S0;
                            }
                        }
                        else if (btnDbTapTimer > DoubleTapTime)
                        {
                            // timeout
                            doubleTap = false;
                            btnDbTapState = DoubleTapState.S0;
                        }

                        break;
                    }
                default:
                    {
                        break;
                    }
            }

            return doubleTap;
        }

        #endregion

        #endregion
    }
}
